/*
 * InodeReaderTests.cpp
 *
 *  Created on: Apr 1, 2010
 *      Author: Lukasz Spintzyk
 */

#include <iostream>
#include <iomanip>
#include "ph.h"
#include "Filesystem.h"
#include "BlockGroup.h"
#include "DirectoryReader.h"
#include "DirEntry.h"
#include "FSException.h"

#include <linux/stat.h>
#include <linux/fs.h>
#include <linux/ext2_fs.h>

#include <cppunit/TestRunner.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/extensions/TestFactoryRegistry.h>


class InodeReaderTests: public CPPUNIT_NS::TestCase
{
    CPPUNIT_TEST_SUITE(InodeReaderTests);
		//CPPUNIT_TEST(getLastReservedBlockNumberTest1);
		//CPPUNIT_TEST(getLastReservedBlockNumberTest2);
		///CPPUNIT_TEST(getLastReservedBlockNumberTest3);
		//CPPUNIT_TEST(reserveDirectBlocksTest);
        //CPPUNIT_TEST(reserveIndirectBlocksTest2);
        //CPPUNIT_TEST(reserveIndirectBlocksTest);
        CPPUNIT_TEST(rootDirTest);

    CPPUNIT_TEST_SUITE_END();

public:
    void setUp(void)
    {
        tmpnam(d_buffer);
        fclose(fopen(d_buffer, "w"));

        d_opts.blockSize=1024;
        d_opts.blocksPerGroup=245;
        d_opts.inodesPerGroup = 240;
        d_opts.groupsCount = 1000;
        d_opts.mount.key = "AA::22::33::44::55::66::77::88::01::22::33::44::55::66::77::88";
        d_opts.mount.iv = "1122334455667788A1A2A3A4A5A6A7A8";


        d_opts.mount.file = std::string(d_buffer);
        d_fw = Filesystem::setUp(d_opts);
/*
		InodeManager* inodeMgr = d_fw->getInodeReader();

		Inode i;
		inodeMgr->getInode(5, i);

		i.d_ctime = time(NULL);
		i.d_mode = S_IFDIR;
		i.d_gid = 1234;
*/




    }
    void tearDown(void)
    {
    	d_fw.reset();
    	d_fr.reset();
    }

protected:
    void getLastReservedBlockNumberTest1()
    {
    	InodeManager* inodeMgr = d_fw->getInodeReader();
    	InodePtr inode = inodeMgr->createNewInode();

    	CPPUNIT_ASSERT_THROW(inodeMgr->getLastReservedBlockNumber(inode)
							,FSException);

    }

    void getLastReservedBlockNumberTest2()
    {
    	InodeManager* inodeMgr = d_fw->getInodeReader();
    	InodePtr inode = inodeMgr->createNewInode();
    	inodeMgr->reserveBlocksForInode(inode, 1);
    	uint32_t last_reserved_block = 0;
    	CPPUNIT_ASSERT_NO_THROW(
    			last_reserved_block = inodeMgr->getLastReservedBlockNumber(inode) );
    	CPPUNIT_ASSERT(last_reserved_block == inode->d_block[0]);
    }

    void getLastReservedBlockNumberTest3()
    {
    	InodeManager* inodeMgr = d_fw->getInodeReader();
    	InodePtr inode = inodeMgr->createNewInode();
    	inodeMgr->reserveBlocksForInode(inode, 20);

    	CPPUNIT_ASSERT_NO_THROW( inodeMgr->getLastReservedBlockNumber(inode) );
    }

    void rootDirTest()
	{
		InodeManager* inodeMgr = d_fw->getInodeReader();
    	DirectoryReader* dirs = d_fw->getDirectoryReader();
    	dirs->setUpRootDirectory(0,0);

    	InodePtr root =inodeMgr->getInode(EXT2_ROOT_INO);



    	DirEntry e;

    	for(int i =0 ; i < 10000; i++)
    	{
    		char buff[4];
    		sprintf(buff, "%d", i);

    		e.d_name = std::string("Test dir") + buff;
    		e.d_inodeNum = i;
    		dirs->addDirEntry(e, root);
    		inodeMgr->setInode(EXT2_ROOT_INO, root);
    	}

    	d_fw.reset();


		d_fr = Filesystem::read(d_opts.mount);

		inodeMgr = d_fr->getInodeReader();
    	dirs = d_fr->getDirectoryReader();

    	root = inodeMgr->getInode(EXT2_ROOT_INO);
    	boost::shared_ptr<DirectoryIterator> it = dirs->begin(root);

    	e = **it;
    	CPPUNIT_ASSERT(e.d_name == ".");
    	++(*it);
    	e = **it;
    	CPPUNIT_ASSERT(e.d_name == "..");
    	++(*it);

    	uint32_t num = 0;
    	while(it->end())
    	{
    		e = **it;
    		CPPUNIT_ASSERT(e.d_inodeNum == num);
    		num++;
    		++(*it);
    	}


    	CPPUNIT_ASSERT_THROW(
    			dirs->path2Inode("/usr/ala ma kota/wp.pl/\"linux/as/a/service\"/as.txt"), FSException);

    	//std::cout << "Inode :" << inode << std::endl;
    	InodePtr inode = dirs->path2Inode(".");
    	//TODO check this path upper
    	//std::cout << "Inode :" << inode << std::endl;
    	CPPUNIT_ASSERT(inode->d_num == EXT2_ROOT_INO);
    	/*
    	std::cout << std::endl;
		for (; !it->end(); ++(*it))
		{
			DirEntry e = *(*it);
			std::cout << e.d_name << std::endl;
		}
		*/


	}

    void reserveIndirectBlocksTest2()
	{
		InodeManager* inodeMgr = d_fw->getInodeReader();


		InodePtr i = Inode::newInstance();
		//inodeMgr->getInode(5, i);

		i->d_ctime = time(NULL);
		i->d_mode = S_IFDIR;
		i->d_gid = 1234;
		i->d_num = 5;
		inodeMgr->setInode(5, i);
		i = inodeMgr->getInode(5);

		CPPUNIT_ASSERT_EQUAL(i->d_ctime, time(NULL));
		CPPUNIT_ASSERT(i->d_mode == S_IFDIR);
		CPPUNIT_ASSERT(i->d_gid == 1234);


		CPPUNIT_ASSERT(i->d_blocks == 0);

		for(uint32_t b = 0; b < 15; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b] == 0);
		}

		inodeMgr->reserveBlocksForInode(i, 66579);
		CPPUNIT_ASSERT(i->d_blocks == 65804+256+1+255);


	}

    void reserveIndirectBlocksTest()
	{
		InodeManager* inodeMgr = d_fw->getInodeReader();


		InodePtr i = Inode::newInstance();
		//inodeMgr->getInode(5, i);

		i->d_ctime = time(NULL);
		i->d_mode = S_IFDIR;
		i->d_gid = 1234;
		i->d_num = 5;

		inodeMgr->setInode(5, i);
		i = inodeMgr->getInode(5);

		CPPUNIT_ASSERT_EQUAL(i->d_ctime, time(NULL));
		CPPUNIT_ASSERT(i->d_mode == S_IFDIR);
		CPPUNIT_ASSERT(i->d_gid == 1234);

		CPPUNIT_ASSERT(i->d_blocks == 0);
		for(uint32_t b = 0; b < 15; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b] == 0);
		}



		// TODO FREE BLOCKS CHECK and inodes


		inodeMgr->reserveBlocksForInode(i, 12);
		CPPUNIT_ASSERT(i->d_gid == 1234);
		for(uint32_t b = 0; b < 12; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b]);
		}

		for(uint32_t b = 12; b < 15; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b] == 0);
		}
		CPPUNIT_ASSERT(i->d_size == 0);
		CPPUNIT_ASSERT(i->d_blocks == 12);

		for (uint32_t b = 0; b < 12; ++b)
		{
			//std::cout << "  " << i.d_block[b] << std::endl;
		}

		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 12);
		CPPUNIT_ASSERT(i->d_block[12]);

		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 13);

		inodeMgr->reserveBlocksForInode(i, 5);
		CPPUNIT_ASSERT(i->d_blocks == 18);

		inodeMgr->reserveBlocksForInode(i, 250);
		CPPUNIT_ASSERT(i->d_blocks == 268);
		CPPUNIT_ASSERT(i->d_block[13]==0);

		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_block[13]);
		CPPUNIT_ASSERT(i->d_blocks == 268);

		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 268);

		//add first entry to first double indirect block
		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 269);

		//add 255 entry to first double indirect block
		inodeMgr->reserveBlocksForInode(i, 255);
		CPPUNIT_ASSERT(i->d_blocks == 524);

		// add non leaf double indirect entry
		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 524);

		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 525);

		inodeMgr->reserveBlocksForInode(i, 200);
		CPPUNIT_ASSERT(i->d_blocks == 725);

		inodeMgr->reserveBlocksForInode(i, 55);
		CPPUNIT_ASSERT(i->d_blocks == 780);

		//256-2 of non leaf + 254*256 entries
		d_fw->getSuperBlock().d_freeBlocks;
		inodeMgr->reserveBlocksForInode(i, 254*(1+256));

		//std::cout << "BlocksCount " << d_fw->getSuperBlock().d_blocksCount << std::endl;
		//std::cout << " i.d_blocks " << i.d_blocks << "\n Free blocks "<< d_fw->getSuperBlock().d_freeBlocks<< std::endl;


		CPPUNIT_ASSERT(i->d_blocks == 780+254*256); // 65804

		CPPUNIT_ASSERT(i->d_block[14]==0);
		//adding double indirect leaf
		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 65804);
		CPPUNIT_ASSERT(i->d_block[14]);

		//adding second double indirect leaf
		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 65804);

		//adding third double indirect leaf
		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 65804);

		inodeMgr->reserveBlocksForInode(i, 256);
		CPPUNIT_ASSERT(i->d_blocks == 65804+256);

		//add double indirect leaf
		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 65804+256);

		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_blocks == 65804+256+1);

		inodeMgr->reserveBlocksForInode(i, 256);
		CPPUNIT_ASSERT(i->d_blocks == 65804+256+1+255);

		d_fw.reset();


		d_fr = Filesystem::read(d_opts.mount);
	}


    void reserveDirectBlocksTest()
	{
		InodeManager* inodeMgr = d_fw->getInodeReader();


		InodePtr i = Inode::newInstance();
		//inodeMgr->getInode(5, i);

		i->d_ctime = time(NULL);
		i->d_mode = S_IFDIR;
		i->d_gid = 1234;
		i->d_num = 5;

		inodeMgr->setInode(5, i);
		i->d_num = 5;
		i = inodeMgr->getInode(5);

		CPPUNIT_ASSERT_EQUAL(i->d_ctime, time(NULL));
		CPPUNIT_ASSERT(i->d_mode == S_IFDIR);
		CPPUNIT_ASSERT(i->d_gid == 1234);

		CPPUNIT_ASSERT(i->d_blocks == 0);
		for(uint32_t b = 0; b < 15; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b] == 0);
		}


		inodeMgr->reserveBlocksForInode(i, 1);
		//TODO CPPUNIT_ASSERT_EQUAL(i.d_ctime, time(NULL));
		CPPUNIT_ASSERT(i->d_mode == S_IFDIR);
		CPPUNIT_ASSERT(i->d_gid == 1234);

		CPPUNIT_ASSERT(i->d_block[0]);

		for(uint32_t b = 1; b < 15; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b] == 0);
		}

		CPPUNIT_ASSERT(i->d_size == 0);
		CPPUNIT_ASSERT(i->d_blocks == 1);


		inodeMgr->reserveBlocksForInode(i, 1);
		CPPUNIT_ASSERT(i->d_gid == 1234);
		for(uint32_t b = 0; b < 2; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b]);
		}

		for(uint32_t b = 2; b < 15; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b] == 0);
		}
		CPPUNIT_ASSERT(i->d_size == 0);
		CPPUNIT_ASSERT(i->d_blocks == 2);


		// TODO FREE BLOCKS CHECK


		inodeMgr->reserveBlocksForInode(i, 10);
		CPPUNIT_ASSERT(i->d_gid == 1234);
		for(uint32_t b = 0; b < 12; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b]);
		}

		for(uint32_t b = 12; b < 15; b++)
		{
			CPPUNIT_ASSERT(i->d_block[b] == 0);
		}
		CPPUNIT_ASSERT(i->d_size == 0);
		CPPUNIT_ASSERT(i->d_blocks == 12);





		d_fw.reset();


		d_fr = Filesystem::read(d_opts.mount);
	}


private:


    void setTestBlock(byte* block, int w, int h, int seed=1)
    {
        int k = seed;
        for (int i = 0; i < w; ++i)
        {
            for (int j = 0; j < h; ++j)
            {
                block[i * h + j] = k++;
            }
        }
    }

    void printBlock(byte* block, int w, int h)
    {
        for (int j = 0; j < h; ++j)
        {
            for (int i = 0; i < w; ++i)
            {
                std::cout << std::hex << std::setw(4) << (unsigned int) block[i * h + j]
                        << " ";
            }
            std::cout << std::endl;
        }
    }


    char d_buffer[L_tmpnam];
    char d_bufferBlock[8*1024*10];
    boost::shared_ptr<Filesystem> d_fw;
    boost::shared_ptr<Filesystem> d_fr;
    FilesystemMkfsArgs d_opts;
    static const char* tempTekst;

};

const char* InodeReaderTests::tempTekst = "Ala ma kota kot ma ale";

CPPUNIT_TEST_SUITE_REGISTRATION(InodeReaderTests);

int main(int ac, char **av)
{
    //--- Create the event manager and test controller
    CPPUNIT_NS::TestResult controller;
    //--- Add a listener that colllects test result
    CPPUNIT_NS::TestResultCollector result;
    controller.addListener(&result);
    //--- Add a listener that print dots as test run.
    CPPUNIT_NS::BriefTestProgressListener progress;
    controller.addListener(&progress);
    //--- Add the top suite to the test runner
    CPPUNIT_NS::TestRunner runner;
    runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());
    runner.run(controller);

    return result.wasSuccessful() ? 0 : 1;
}
